using System;
using System.Runtime.InteropServices;

namespace GameFrame.Deterministic
{
  /// <summary>
  /// A serializable equivalent of Nullable&lt;FPVector3&gt;.
  /// </summary>
  /// 
  ///             \ingroup MathAPI
  [Serializable]
  [StructLayout(LayoutKind.Explicit)]
  public struct NullableFPVector3
  {
    public const int SIZE = 32;
    [FieldOffset(0)] public long _hasValue;
    [FieldOffset(8)] public FPVector3 _value;

    /// <summary>Returns true if this nullable has a value.</summary>
    public bool HasValue => this._hasValue == 1L;

    /// <summary>Returns current value.</summary>
    /// <exception cref="T:System.NullReferenceException">If <see cref="P:Photon.Deterministic.NullableFPVector3.HasValue" /> is false</exception>
    public FPVector3 Value
    {
      get
      {
        if (this._hasValue == 0L)
          throw new NullReferenceException();
        return this._value;
      }
    }

    /// <summary>
    /// If <see cref="P:Photon.Deterministic.NullableFPVector3.HasValue" /> is true, returns <see cref="P:Photon.Deterministic.NullableFPVector3.Value" />. Otherwise returns <paramref name="v" />.
    /// </summary>
    /// <param name="v"></param>
    /// <returns></returns>
    public FPVector3 ValueOrDefault(FPVector3 v) => this._hasValue != 1L ? v : this.Value;

    public static implicit operator NullableFPVector3(FPVector3 v) => new NullableFPVector3()
    {
      _value = v,
      _hasValue = 1
    };

    public static unsafe void Serialize(
      NullableFPVector3* ptr,
      IDeterministicFrameSerializer serializer)
    {
      if (serializer.Writing)
      {
        serializer.Stream.WriteFPVector3(ptr->_value);
        serializer.Stream.WriteBoolean(ptr->_hasValue == 1L);
      }
      else
      {
        ptr->_value = serializer.Stream.ReadFPVector3();
        ptr->_hasValue = serializer.Stream.ReadBool() ? 1L : 0L;
      }
    }

    public override int GetHashCode() => this._hasValue == 0L ? 0 : this._value.GetHashCode();
  }
}